package com.nulldev.util.fileformats.ftf;

import java.io.File;
import java.util.HashMap;
import java.util.regex.Pattern;

import javax.swing.JOptionPane;

import com.nulldev.util.crypto.Compressor;
import com.nulldev.util.crypto.encryptAPI.manage.EncryptAPI;
import com.nulldev.util.encoding.base64.Base64;
import com.nulldev.util.error.ErrorCallback;
import com.nulldev.util.io.FileUtil;
import com.nulldev.util.io.MD5Utils;
import com.nulldev.util.java.swing.SwingUtil;
import com.nulldev.util.java.swing.extSwing.eInputDialog;
import com.nulldev.util.java.swing.extSwing.resources.eMessageType;
import com.nulldev.util.other.BooleanUtil;
import com.nulldev.util.settings.FormattedDataFile;

public class FTFReader {
	
	public static FTFDocument parse(final File f, final ErrorCallback callback) {
		try {
			final FormattedDataFile fdf = new FormattedDataFile(f);
			final HashMap<String, String> meta = fdf.getSection("metadata");
			final EncryptionType enct = EncryptionType.getType(meta.get("encryptionType"));
			if (enct == null) {
				callback.onError(new FTFDecryptionError("FTF.encodeFile() -> No encryption type specified!"));
				return null;
			}
			String key = meta.get("key");
			if (!enct.equals(EncryptionType.Compressed)) {
				if (key == null) {
					if (SwingUtil.isX11Safe()) {
						final eInputDialog eip = eInputDialog.main(eMessageType.WARNING);
						eip.setWindowTitle("FTF: Password required");
						eip.setMessageTitle("Encrypted file");
						eip.setMessage("Enter the password to the file.");
						eip.waitForOK();
						final String password = eip.getTextInput(); //JOptionPane.showInputDialog(null, "Enter file password.", "FTF Reader", JOptionPane.WARNING_MESSAGE);
						if (password == null || password.length() == 0) {
							callback.onError(new FTFDecryptionError("FTF.encodeFile().decryptFile() -> No password specified!"));
							return null;
						}
						key = password;
					}else {
						callback.onError(new FTFDecryptionError("FTF.encodeFile().decryptFile() -> No password specified!"));
						return null;
					}
				}
			}
			final String hash = meta.get("hash");
			final boolean forceHashCheck = BooleanUtil.safeParse(meta.get("forceHashCheck"));
			final HashMap<String, String> _data = fdf.getSection("data");
			if (_data.size() == 0) {
				callback.onError(new FTFMissingData("FTF.encodeFile() -> No data found from parsing!"));
				return null;
			}
			final String data = _data.get("fileData");
			final String dataHash = MD5Utils.getMD5Hex(data);
			if (forceHashCheck) {
				if (!dataHash.equals(hash)) {
					callback.onError(new FTFHashMismatch("FTF.encodeFile().verify() -> MD5 Hash mismatch!"));
					return null;
				}
			}else if (!dataHash.equals(hash)) {
			}
			
			String decodedData = data;
			if (enct == EncryptionType.Compressed) {
				decodedData = Compressor.decompress(data);
			}else if (enct == EncryptionType.AES) {
				decodedData = EncryptAPI.getEncryptorForEncryption("AES").decrypt(data, key);
			}else if (enct == EncryptionType.DES) {
				decodedData = EncryptAPI.getEncryptorForEncryption("DES").decrypt(data, key);
			}else if (enct == EncryptionType.XOR) {
				decodedData = EncryptAPI.getEncryptorForEncryption("XOR").decrypt(data, key);
			}else if (enct == EncryptionType.XXTEA) {
				decodedData = EncryptAPI.getEncryptorForEncryption("XXTEA").decrypt(data, key);
			}
			if (decodedData == null || decodedData.length() == 0 || decodedData.equals(data)) {
				callback.onError(new FTFMissingData("FTF.encodeFile().decodeFile() -> No data found from decrypting!"));
				return null;
			}

			final FTFDocument doc = new FTFDocument(decodedData);
			if (meta.containsKey("fileData")) {
				meta.remove("fileData");
			}
			doc.setMetadata(meta);
			return doc;
		}catch (Exception e) {
			callback.onError(e);
			return null;
		}
	}
	
	public static class FTFHashMismatch extends RuntimeException {

		public FTFHashMismatch(String string) {
			super(string);
		}
	}
	
	public static class FTFMissingData extends RuntimeException {

		public FTFMissingData(String string) {
			super(string);
		}
	}
	
	public static class FTFDecryptionError extends RuntimeException {

		public FTFDecryptionError(String string) {
			super(string);
		}
	}
	
	public static enum EncryptionType {
		Compressed("Compress", "LiteString"),
		AES("AES", "AES"),
		DES("DES", "DES"),
		XXTEA("XXTEA", "XXTEA"),
		XOR("XOR", "XOR"),
		;
		
		private final String IN_FILE_VALUE, encryptAPIE;
		private EncryptionType(final String IN_FILE_VALUE, final String encryptAPI_Encryptor) {
			this.IN_FILE_VALUE = IN_FILE_VALUE;
			this.encryptAPIE = encryptAPI_Encryptor;
		}
		
		public String getFileValue() {
			return this.IN_FILE_VALUE;
		}
		
		public String getEncryptorTag() {
			return this.encryptAPIE;
		}
		
		public static EncryptionType getType(final String type) {
			if (type == null) {
				return null;
			}
			for (final EncryptionType v : values()) {
				if (v.getFileValue().equals(type)) {
					return v;
				}
			}
			return null;
		}
	}
	
	public static void main() {
		final FTFDocument doc = parse(FileUtil.safeCreate("[redacted]"), new ErrorCallback() {
			
			@Override
			public void onError(Throwable t) {
				throw new RuntimeException(t);
			}
		});
		if (doc != null) {
			System.out.println(doc.toHTML());
		}
	}
}
